package com.free.semantic.core.common;

import com.alibaba.fastjson.JSONObject;
import com.free.semantic.common.Ls;
import com.free.semantic.common.RelationItem;
import com.free.semantic.core.ConceptInstanceManage;
import com.free.semantic.core.ConceptManage;
import com.free.semantic.core.RelationFunction;
import com.free.semantic.core.common.serializeimpl.BaseSerializeImpl;
import com.free.semantic.core.common.serializeimpl.SerializeFunctionGroup;
import com.free.semantic.core.common.serializeimpl.baseimpl.ConstMap;
import com.free.semantic.core.common.serializeimpl.baseimpl.MultipleKeyFuncMap;
import com.free.semantic.core.common.serializeimpl.baseimpl.SourceMap;
import com.free.semantic.core.concept.SubConceptManage;
import com.free.semantic.core.funcimpl.ConstMapImpl;
import com.free.semantic.core.funcimpl.MultipleKeyFuncImpl;
import com.free.semantic.core.funcimpl.SourceMapImpl;
import com.free.semantic.core.impl.ConceptInstanceManageImpl;
import com.free.semantic.core.impl.ConceptManageImpl;
import com.free.semantic.core.proxy.ProxyFunctionProvider;
import com.free.semantic.core.proxy.impl.ConceptInsManageProxy;
import com.free.semantic.core.script.SemanticNetEngine;
import com.free.semantic.core.script.impl.ScriptEngineImpl;
import com.free.semantic.core.service.ConceptGroupManage;
import com.free.semantic.core.takeclient.ConceptInsTakeCommand;
import com.free.semantic.core.takeclient.impl.LocalManageImpl;
import com.free.semantic.core.takeclient.impl.UrlClientImpl;
import com.free.semantic.model.conceptmodel.*;
import com.free.semantic.req.RelationDefine;
import com.free.semantic.req.RelationDefineGroup;
import com.free.semantic.utils.Utils;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.free.semantic.core.concept.impl.ConceptSelfInfoConcept.FIND_MORE;
import static com.free.semantic.core.concept.impl.ConceptSelfInfoConcept.FIND_ONE;
import static com.free.semantic.utils.Utils.*;
import static com.free.semantic.utils.Utils.createRelation2;

@Data
public class UserConceptManageInfo implements ConceptGroupManage {

    private static final String THREE_FIND_ONE = "threeFindOne";

    private static final String TWO_FIND_ONE = "twoFindOne";

    private static final String MORE_FIND_ONE = "moreFindOne";

    private String baseUrl;

    Map<String, SemanticNetwork> entityMap = new LinkedHashMap<>();

    Map<String, ConceptInstanceManage> manageMap = new LinkedHashMap<>();

    Map<String, String> conceptMangeURL = new HashMap<>();

    Map<String, Long> manageMapId = new LinkedHashMap<>();

    SemanticNetEngine scriptEngine = new ScriptEngineImpl();

    ConceptInstanceManage instanceManage;

    SerializeFunctionGroup serializeFunctionGroup = new SerializeFunctionGroup();

    private Long userId;

    private BiFunction<Long, SemanticNetworkDelta, SemanticNetwork> saveAction;

    private Consumer<Long> deleteAction;

    public UserConceptManageInfo(String baseUrl, Long userId,
                                 BiFunction<Long, SemanticNetworkDelta, SemanticNetwork> saveAction, Consumer<Long> deleteAction) {
        this.baseUrl = baseUrl;
        this.userId = userId;
        this.saveAction = saveAction;
        this.deleteAction = deleteAction;
        buildBaseManage();
    }

    @Override
    public ConceptInstanceManage getManage(String conceptName) {
        if (StringUtils.isBlank(conceptName)) {
            return instanceManage;
        } else {
            return manageMap.get(conceptName);
        }
    }

    public void addConcept(String conceptName, SemanticNetwork network) {
        this.entityMap.put(conceptName, network);
        this.manageMap.put(conceptName, convertToInstance(network));
        this.conceptMangeURL.put(conceptName, baseUrl + String.format("/manage/controller/user/%s/semantic/", conceptName));
    }

    public void save(String conceptName) {
        SemanticNetwork network = this.saveAction.apply(userId, merge(conceptName, entityMap.get(conceptName), manageMap.get(conceptName)));
        this.entityMap.put(conceptName, network);
    }

    Set<String> getConceptNameList() {
        return SubConceptManage.getConceptLs();
    }

    Set<String> getManageNameList() {
        return SubConceptManage.getRelationNameLs();
    }

    private boolean isManageConcept(String conceptName) {
        return getConceptNameList().contains(conceptName);
    }

    private boolean isManageRelationDefine(String relationName) {
        return getManageNameList().contains(relationName);
    }

    private SemanticNetworkDelta merge(String name, SemanticNetwork source, ConceptInstanceManage current) {

        if (source == null) {
            source = new SemanticNetwork();
        }
        ConceptManage concept = current.concept();

        List<String> allObjects = concept.getAllObjects();
        Set<String> conceptNameList = getConceptNameList();

        allObjects.removeAll(conceptNameList);

        List<RelationItem> relationItemLs = new ArrayList<>();

        Map<RelationItem, RelationFunction> relationImpls = new HashMap<>();
        Map<RelationItem, Long> relationIdMap = new HashMap<>();

        for (RelationItemEntity relationItemEntity : source.getRelationItemEntity()) {
            relationIdMap.put(relationItemEntity.toRelationItem(), relationItemEntity.getId());
        }

        Utils.forEachRelationItem(concept, item -> {
            if (!conceptNameList.containsAll(item.getSourceObjList())
                    || !conceptNameList.contains(item.getDestObj())) {
                relationItemLs.add(item);
                RelationFunction function = current.getFunction(item.getSourceObjectList(), item.getDestObject());
                if (function != null) {
                    relationImpls.put(item, function);
                }
            }
        });

        List<RelationDefine> allRelationDefine = concept.getAllRelationDefine();
        allRelationDefine.removeIf(define -> isManageRelationDefine(define.getName()));

        SemanticNetworkDelta delta = new SemanticNetworkDelta();
        delta.setId(source.getId());
        delta.setName(name);
        DateChange<ConceptObject> conceptObjectDateChange = objectDelta(source.getConceptObjects(), allObjects);
        delta.setConceptObjectChange(conceptObjectDateChange);
        delta.setRelationItemEntityChange(relationItemDateChange(source.getId(), source.getRelationItemEntity(), relationItemLs));
        delta.setRelationDefineEntityDateChange(relationDefineChange(source.getId(), source.getRelationDefineEntities(), allRelationDefine));
        delta.setRelationImplEntityDateChange(relationImplDateChange(source.getId(), relationIdMap, source.getItemImplEntityMap(), relationImpls));
        return delta;
    }

    private DateChange<ConceptObject> objectDelta(List<ConceptObject> conceptObjects, List<String> allObjects) {
        DateChange<ConceptObject> result = new DateChange<>();
        List<ConceptObject> newObjects = new ArrayList<>();
        Map<String, ConceptObject> conceptObjectMap = conceptObjects.stream().collect(Collectors.toMap(ConceptObject::getName, Function.identity()));

        for (String objName : allObjects) {
            if (conceptObjectMap.containsKey(objName)) {
                conceptObjectMap.remove(objName);
            } else {
                newObjects.add(new ConceptObject(objName));
            }
        }
        List<ConceptObject> deleteList = new ArrayList<>(conceptObjectMap.values());
        result.setAdd(newObjects);
        result.setDeleteIdList(deleteList);
        return result;
    }

    private DateChange<RelationItemEntity> relationItemDateChange(Long semanticId, List<RelationItemEntity> relationItemEntities, List<RelationItem> relationItems) {
        DateChange<RelationItemEntity> result = new DateChange<>();
        Map<RelationItem, RelationItemEntity> itemEntityMap = relationItemEntities.stream().collect(Collectors.toMap(RelationItemEntity::toRelationItem, Function.identity()));

        List<RelationItemEntity> newObjects = new ArrayList<>();

        for (RelationItem objName : relationItems) {
            if (itemEntityMap.containsKey(objName)) {
                itemEntityMap.remove(objName);
            } else {
                newObjects.add(new RelationItemEntity(semanticId, objName));
            }
        }
        List<RelationItemEntity> deleteList = new ArrayList<>(itemEntityMap.values());
        result.setAdd(newObjects);
        result.setDeleteIdList(deleteList);
        return result;
    }

    private DateChange<RelationDefineEntity> relationDefineChange(Long semanticId, List<RelationDefineEntity> conceptObjects, List<RelationDefine> allObjects) {
        DateChange<RelationDefineEntity> result = new DateChange<>();

        Map<String, RelationDefineEntity> conceptObjectMap = conceptObjects.stream().collect(Collectors.toMap(RelationDefineEntity::getName, Function.identity()));
        List<RelationDefineEntity> newObjects = new ArrayList<>();

        for (RelationDefine objName : allObjects) {
            if (conceptObjectMap.containsKey(objName.getName())) {
                conceptObjectMap.remove(objName.getName());
            } else {
                newObjects.add(new RelationDefineEntity(semanticId, objName));
            }
        }
        result.setAdd(newObjects);
        return result;
    }

    private JSONObject convertToJSON(RelationItem relation, RelationFunction function) {
        BaseSerializeImpl function1 = convert(relation, function);
        String implType = function1.getImplType();
        JSONObject parameter = function1.getParameter();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("type", implType);
        jsonObject.put("parameter", parameter);
        return jsonObject;
    }

    private BaseSerializeImpl convert(RelationItem relation, RelationFunction function) {
        if (function instanceof ConstMapImpl) {
            return new ConstMap((ConstMapImpl) function);
        }
        if (function instanceof MultipleKeyFuncImpl) {
            return new MultipleKeyFuncMap((MultipleKeyFuncImpl) function);
        }

        if (function instanceof SourceMapImpl) {
            return new SourceMap((SourceMapImpl) function);
        }
        if (function instanceof BaseSerializeImpl) {
            return (BaseSerializeImpl) function;
        }
        throw new RuntimeException(String.format("关系 %s 对应的 实现不支持序列化 ", relation));
    }


    private DateChange<RelationImplEntity> relationImplDateChange(Long semanticId,
                                                                  Map<RelationItem, Long> relationIdMap,
                                                                  Map<RelationItem, RelationImplEntity> relationImplEntities,
                                                                  Map<RelationItem, RelationFunction> relationImpls) {
        DateChange<RelationImplEntity> result = new DateChange<>();

        List<RelationImplEntity> newObjects = new ArrayList<>();
        List<RelationImplEntity> deleteList = new ArrayList<>();
        List<RelationImplEntity> updateList = new ArrayList<>();

        for (RelationItem relation : relationImpls.keySet()) {
            RelationFunction relationFunction = relationImpls.get(relation);
            JSONObject implJsonObject = convertToJSON(relation, relationFunction);
            if (relationImplEntities.containsKey(relation)) {
                relationImplEntities.remove(relation);
                RelationImplEntity relationImplEntity = relationImplEntities.get(relation);
                JSONObject json = relationImplEntity.toJSON();
                if (!implJsonObject.equals(json)) {
                    relationImplEntity.setImplStr(implJsonObject.toJSONString());
                    updateList.add(relationImplEntity);
                }
            } else {
                RelationImplEntity relationImplEntity = new RelationImplEntity();
                relationImplEntity.setSemanticNetworkId(semanticId);
                relationImplEntity.setImplStr(implJsonObject.toJSONString());
                relationImplEntity.setRelationItemId(relationIdMap.get(relation));
                newObjects.add(relationImplEntity);
            }
        }
        result.setAdd(newObjects);
        result.setDeleteIdList(deleteList);
        result.setUpdate(updateList);
        return result;
    }

    private ConceptInstanceManage convertToInstance(SemanticNetwork network) {

        ConceptManage conceptManage = new ConceptManageImpl();
        network.getConceptObjects().forEach(conceptObject -> {
            conceptManage.addConcept(conceptObject.getName());
        });

        Map<Long, RelationItem> itemMap = new HashMap<>();

        for (RelationItemEntity relationItemEntity : network.getRelationItemEntity()) {
            RelationItem relationItem = relationItemEntity.toRelationItem();
            itemMap.put(relationItemEntity.getId(), relationItem);
            conceptManage.instanceRelation(Ls.of(relationItem));
        }

        List<RelationDefineEntity> relationDefineEntities = network.getRelationDefineEntities();

        relationDefineEntities.forEach(relationDefineEntity -> {
            RelationDefine relationDefine = relationDefineEntity.toRelationDefine();
            conceptManage.addRelationDefine(relationDefineEntity.getName(), relationDefine.getRelaItemDefs());
        });

        ConceptInstanceManage instanceManage = new ConceptInstanceManageImpl(conceptManage);

        Map<RelationItem, RelationImplEntity> itemEntityMap = network.getItemImplEntityMap();

        for (Map.Entry<RelationItem, RelationImplEntity> entityEntry : itemEntityMap.entrySet()) {

            RelationItem relationItem = entityEntry.getKey();
            RelationImplEntity value = entityEntry.getValue();
            String implStr = value.getImplStr();

            JSONObject jsonObject = JSONObject.parseObject(implStr);
            String string = jsonObject.getString("type");
            JSONObject parameter = jsonObject.getJSONObject("parameter");

            RelationFunction functionByTYpe = serializeFunctionGroup.getFunctionByTYpe(string, parameter);
            instanceManage.registerFunction(relationItem.getSourceObjectList(), relationItem.getDestObject(), functionByTYpe);
        }

        return instanceManage;
    }


    @Override
    public void add(String conceptName, ConceptInstanceManage manage) {
        manageMap.put(conceptName, manage);
    }

    public void buildBaseManage() {
        RelationDefineGroup group = new RelationDefineGroup();

        defineRelation(group, TWO_FIND_ONE, defineItem(oneObj("A", "B"), moreObj("C")));

        defineRelation(group, THREE_FIND_ONE, defineItem(oneObj("A", "B", "C"), moreObj("D")));

        defineRelation(group, MORE_FIND_ONE, defineItem(moreObj("A"), oneObj("B")));

        ConceptManage model = getConceptManage(group);

        object(model, "@语义网络组", "语义网络组", "语义网络", "语义网络实现URL", "语义网络名称",
                "创建", "复制", "删除", "导入", "注册实现器", "语义网络脚本", "执行", "实现器", "实现器名称", "保存");

        createRelation(model, FIND_MORE, "语义网络组", "语义网络名称");
        createRelation2(model, TWO_FIND_ONE, "@语义网络组", "语义网络名称", "创建");
        createRelation2(model, FIND_ONE, "语义网络名称", "语义网络");
        createRelation2(model, FIND_ONE, "语义网络", "复制");
        createRelation2(model, FIND_ONE, "语义网络", "删除");
        createRelation2(model, FIND_ONE, "语义网络", "保存");
        createRelation2(model, FIND_ONE, "语义网络名称", "语义网络实现URL");

        createRelation2(model, THREE_FIND_ONE, "@语义网络组", "语义网络名称", "语义网络实现URL", "导入");
        createRelation2(model, THREE_FIND_ONE, "语义网络", "实现器", "实现器名称", "注册实现器");
        createRelation2(model, TWO_FIND_ONE, "语义网络名称", "语义网络脚本", "执行");

        instanceManage = new ConceptInstanceManageImpl(model);

        instanceManage.registerFunction(Ls.of("语义网络组"), "语义网络名称",
                context -> {
                    List<String> conceptNameList = new ArrayList<>(manageMap.keySet());
                    context.setValue("语义网络名称", conceptNameList);
                });

        instanceManage.registerFunction(Ls.of("@语义网络组", "语义网络名称"), "创建",
                context -> {
                    String name = (String) context.getListValue("语义网络名称").get(0);
                    if (manageMap.containsKey(name)) {
                        throw new RuntimeException(String.format("已经存在名为%s的语义网络", name));
                    }
                    conceptMangeURL.put(name, baseUrl + String.format("/manage/controller/user/%s/semantic/", name));
                    ConceptManage conceptManage = new ConceptManageImpl();
                    ConceptInstanceManage ins = new ConceptInstanceManageImpl(conceptManage);
                    manageMap.put(name, ins);
                    context.setValue("创建", "success");
                });

        instanceManage.registerFunction(Ls.of("语义网络名称"), "语义网络",
                context -> {
                    String name = (String) context.getListValue("语义网络名称").get(0);
                    if (!manageMap.containsKey(name)) {
                        throw new RuntimeException(String.format("不存在名为%s的语义网络", name));
                    }
                    context.setValue("语义网络", name);
                });

        instanceManage.registerFunction(Ls.of("语义网络"), "复制",
                context -> {
                    String name = (String) context.getListValue("语义网络").get(0);
                    if (!manageMap.containsKey(name)) {
                        throw new RuntimeException(String.format("不存在名为%s的语义网络", name));
                    }
                    ConceptInstanceManage conceptInstanceManage = manageMap.get(name);
                    ConceptInsTakeCommand client = new LocalManageImpl(conceptInstanceManage);

                    SemanticNetEngine engine = new ScriptEngineImpl();
                    ConceptManage conceptManage = engine.buildManageFromTake(client);


                    ProxyFunctionProvider provider = new ProxyFunctionProvider(conceptManage, conceptInstanceManage);
                    provider.prepareFunctionMap();

                    ConceptInstanceManage ins = new ConceptInstanceManageImpl(conceptManage, provider);

                    manageMap.put(name + "副本", ins);
                    conceptMangeURL.put(name + "副本", baseUrl + String.format("/manage/controller/user/%s/semantic/", name + "副本"));
                    context.setValue("复制", "success");
                });

        instanceManage.registerFunction(Ls.of("语义网络"), "删除",
                context -> {
                    String name = (String) context.getListValue("语义网络").get(0);
                    if (!manageMap.containsKey(name)) {
                        throw new RuntimeException(String.format("不存在名为%s的语义网络", name));
                    }
                    manageMap.remove(name);
                    conceptMangeURL.remove(name);
                    context.setValue("删除", "success");
                    SemanticNetwork semanticNetwork = entityMap.get(name);
                    if (semanticNetwork != null) {
                        deleteAction.accept(semanticNetwork.getId());
                        entityMap.remove(name);
                    }
                });

        instanceManage.registerFunction(Ls.of("语义网络"), "保存",
                context -> {
                    String name = (String) context.getListValue("语义网络").get(0);
                    if (!manageMap.containsKey(name)) {
                        throw new RuntimeException(String.format("不存在名为%s的语义网络", name));
                    }
                    save(name);
                    context.setValue("保存", "success");
                });

        instanceManage.registerFunction(Ls.of("语义网络名称"), "语义网络实现URL",
                context -> {
                    String name = (String) context.getListValue("语义网络名称").get(0);
                    if (!manageMap.containsKey(name)) {
                        throw new RuntimeException(String.format("不存在名为%s的语义网络", name));
                    }
                    String url = conceptMangeURL.get(name);
                    context.setValue("语义网络实现URL", url);
                });

        instanceManage.registerFunction(Ls.of("@语义网络组", "语义网络名称", "语义网络实现URL"), "导入",
                context -> {
                    String name = (String) context.getListValue("语义网络名称").get(0);
                    if (manageMap.containsKey(name)) {
                        throw new RuntimeException(String.format("已经存在名为%s的语义网络", name));
                    }
                    String url = (String) context.getListValue("语义网络实现URL").get(0);
                    UrlClientImpl urlClient = new UrlClientImpl(url);
                    ConceptInsManageProxy proxy = new ConceptInsManageProxy(urlClient);
                    manageMap.put(name, proxy);
                    conceptMangeURL.put(name, baseUrl + String.format("/manage/controller/user/%s/semantic/", name));
                    context.setValue("导入", "success");
                });

        instanceManage.registerFunction(Ls.of("语义网络", "实现器", "实现器名称"), "注册实现器",
                context -> {
                    String name = (String) context.getListValue("语义网络").get(0);
                    if (!manageMap.containsKey(name)) {
                        throw new RuntimeException(String.format("不存在名为%s的语义网络", name));
                    }
                    String url = (String) context.getListValue("实现器").get(0);
                    String providerName = (String) context.getListValue("实现器名称").get(0);
                    UrlClientImpl client = new UrlClientImpl(url);

                    ConceptInsManageProxy proxy = new ConceptInsManageProxy(client);
                    manageMap.get(name).registerFunction(providerName, relationItem ->
                            proxy.getFunction(relationItem.getSourceObjectList(), relationItem.getDestObject()));
                    context.setValue("注册实现器", "success");
                });

        instanceManage.registerFunction(Ls.of("语义网络名称", "语义网络脚本"), "执行",
                context -> {
                    String name = (String) context.getListValue("语义网络名称").get(0);
                    if (!manageMap.containsKey(name)) {
                        throw new RuntimeException(String.format("不存在名为%s的语义网络", name));
                    }
                    String script = (String) context.getListValue("语义网络脚本").get(0);
                    ConceptInstanceManage manage = manageMap.get(name);
                    Object object = scriptEngine.execScript(manage, script);
                    context.setValue("执行", object);
                });
    }

}
